Components › Navigation › Section top bar › Section top bar

Section top bar

canon

Composable bar with title, chips, search, dividers and spacer.

Four screens hand-roll the identity instead of SectionTopBarTitle — decision A6. Phone two-step (AD 27 Sep 2026, acrm !3997, fix/nastaveni-mobil-dvoukrok): Nastavení (SettingsLayout.tsx) below the 720px breakpoint drops the side rail and becomes ⌘K's own two-step — `/nastaveni` with no section shows only the grouped menu full-width (`.nastaveni-page.m-menu`), `/nastaveni/<sekce>` shows only that section's `SectionTopBar` (`flat`) and body (`.m-section`) with a quiet `IconButton icon="arrow-left"` (`.set-back`) as the bar's first item, wired to `navigate('/nastaveni')`. Desktop keeps rail + content side by side unchanged. Side rail at ≥720px would take ~260px on a 390px phone and clip every section's fields — the same two-step shape as the ⌘K palette's list→detail step, not a new pattern. acrm !4029 (27 Sep 2026): new app-only layer apps/web/src/ds/section-top-bar.css (home for bar rules; ds.css is over the 100 kB ratchet and must not grow) — a long name in the bar stays on one line with an ellipsis and SectionTopBarTitle carries `title` with the full name; switch labels in the bar do not wrap. acrm !4044 (27 Sep 2026): the record view switcher on person/company (Activities / Profile) is the canonical `.tchip` with chevron (RULES §8: a switcher in the bar = .tchip + menu), the bespoke `.rvsw-*` was deleted. Menu items without description text (acrm !4051). Phone layout, Attio pattern (owner 2026-09-28, acrm !4088): actions as data — primaryAction {label, icon, onClick, kind} → Fab below 720 px (round only for kind 'create'), secondaryActions (menuOnly, danger, phoneOnly) → one ⋮ bottom sheet, closeAction last; row 1 = title + ⋮, row 2 = tools (view picker left, Filtr/Řadit right). Desktop unchanged. PageHeader forwards the same props. 14 Oct 2026 (owner "schvaleno" 24b v4, proto/detail-jednotne@9600c16a2, acrm !5055; RULES §26): record detail header = ↑↓ · name · ⋮ · ✕ with NO blue primary. Rare actions stay in context (Nový obchod in Nejbližší aktivita + Obchody tab; project Aktualizace in its section); an order shows one button only for the step it waits for. Dev check 14 Oct 1440 + 390: person, company, project, order — 0 primaries in the header, the project label chip gone. Registry repaired 17 Oct 2026 (review F5): PageHeader, RecordCloseButton live in apps/web (see files), not exported by @acrm/ui; dropped from react[]. 20 Oct 2026 (ACRM-569, proto/komunikace-menu-zobrazit-vse@0b7180f3a): `SectionTopBarTitle dot` — identity by colour with no glyph (`.pipe-dot`, a 12 px circle inside the `--tile-sm` frame, CSS in apps/web/src/ds/section-top-bar-dot.css); used by a mail label list. Story: "With dot".

Used in 64 files · 56 areas · 118× — components/ListSearch, components/accounting, components/agent-desk, components/datamart, components/segment, pages/AccountingInvoiceDetailPage …

One bar above every list and every record. It answers, in this order: what am I looking at (identity), what changes what I see (view, filter, search), and what can I do (quiet actions, at most one primary). packages/ui/src/components/SectionTopBar.tsx — SectionTopBar, SectionTopBarChip, SectionTopBarSearch, SectionTopBarDivider, SectionTopBarSpacer; classes .tbar*, .tchip, .vspace in apps/web/src/ds.css. Simple list screens use the PageHeader wrapper.

The bar is opinionated on purpose, and the component warns in development when its rules are broken.

When to use

  • Above any list, any record, any section screen — one screen, one bar.
  • To host the screen's view switch (NavSelect), its filter and sort chips, its search and its actions.

When not to use

  • As the application frame — that is Top bar (.as-top), which never changes with the screen.
  • As a tab row — that is Tab strip, which goes under this bar.
  • As a place for a count, a breadcrumb, or a status sentence.

Anatomy

  • Bar .tbar: one row, hairline underneath (.tbar.flat drops it), slots in fixed order — identity · view/filter controls · SectionTopBarSpacer (.vspace) · actions · primaryAction.
  • Identity: SectionTopBarTitle — exactly one of icon, tile+tone, avatar, heading-dropdown, inline rename (see its own page).
  • Chip .tchip: height: var(--control-h) (30px), padding: 0 10px, gap: 7px, radius-md, var(--small), icon --ico-xs; muted key .k, bold value .v, chevron .chev, open state .tchip.open.
  • Search .tsearch: quiet box that paints its border accent on :focus-within; the input itself carries no ring.
  • Divider .tdiv (1×20px hairline), spacer .vspace (flex: 1).
  • Actions are quiet (icon + verb); the single primaryAction is the only filled button.

Identity by colour — dot (2026-10-20)

SectionTopBarTitle dot="var(--gc-…)" draws a 12 px colour circle inside the tile frame, with no glyph. Use it when the screen's identity is a colour the user chose (a mail label), not a type. Needs no icon; do not combine with tileIcon, kind or avatar.

Phone (below 720 px) — Attio pattern (2026-09-28, acrm !4088)

Only for a bar whose actions are data (primaryAction as { label, icon, onClick, kind? }, secondaryActions, closeAction; .tbar.has-acts):

  • Row 1: title (ellipsis) + ⋮ „Další akce" + closeAction (×). No text buttons.
  • ⋮ opens one bottom sheet with all secondary actions (forceMenu: even a single item opens the sheet); danger rows are red; phoneOnly actions appear only here (e.g. „Uložit pro všechny").
  • Row 2 (view row, owner-approved 28 Sep 2026, acrm !4114): one view picker on the left (NavSelect chip — „Zobrazení ⌄" on Úkoly with Kompaktní / Plné instead of the segmented switch, „Schránka ⌄" on Pošta), icon buttons on the right. Each opens a bottom sheet with a check at the chosen option.
  • Filtr carries the neutral count of conditions (FilterToggleChip count).
  • Seskupit and Řadit carry an accent dot, not a count, when they are off their default (.fb-chip.lst-toggle.tb-dot.set::after, inside the icon's corner so the scrolling tools row does not clip it). A count would widen the icon and the row would overflow at 390 px. The chosen option lives in the accessible name and the tooltip („Řadit: Termín", „Seskupit: Podle stavu").
  • Search is an icon on the right; a tap expands it over the whole row (.tsearch-fill) with × to close; with a typed query it stays open. The view picker steps aside while search is open.
  • Quiet desktop tool icons that do not fit (Pošta: Synchronizovat, Spravovat složky) move into ⋮ as phoneOnly secondary actions.
  • The primary becomes the Fab bottom-right (round only for kind: 'create', otherwise extended with its label).
  • Desktop is unchanged: secondary actions are buttons before the primary, menuOnly actions sit in ⋮, closeAction stays last.
  • A bar still passing a JSX primary keeps it in row 1 until it is converted.

Best practices

Do
  • Give the bar exactly one identity mark — it never starts with bare text.
  • Put everything that changes the view before the spacer, and everything that acts after it.
  • Keep at most one primary action, and let it carry the verb of the screen (Nový obchod).
  • Let the bar scroll sideways on a narrow screen (≤720px) rather than squeezing its controls; the primary action sticks to the right edge.
Don’t
  • A count or "N z M" in the bar — that belongs to the listing footer or a stat tile. (Record navigation arrows are the one documented exception.)
  • A breadcrumb inside the identity.
  • Coloured or filled action buttons other than the one primary.
  • Capitals, and no second bar under this one except the Filter bar or a Tab strip.
  • A hand-built .tbar when the component would do; the escape hatch exists (status star, logo trigger, colour swatch) but it skips every guardrail.

Content

  • Identity = the name of the thing, sentence case, no type prefix.
  • Chips read key value (Řadit Vytvořeno, Filtr 2).
  • Actions are verbs; the primary action names what will be created.
  • title-with-entity-tile — the identity of this bar.
  • filter-bar — the second row under it.
  • tab-strip, record-tabs — the tab row under a record's bar.
  • top-bar — the application frame, not this.
  • Stories: components--section-top-bar--default, --variants, --states, --with-icon-and-kbd, --composition; legacy overview components--section-top-bar.

Record header on the phone (owner approved 1. 10. 2026, acrm !4362, verified on dev)

  • ≤720 px the record header (person, company, deal, task, project…) is ONE row of the normal bar height: ‹ back (44 px, „Zpět na výpis“) · mark + name (truncates) · ⋯ (44 px). The ✕ is not drawn on the phone — ‹ has the same target, Esc too. Desktop unchanged.
  • The view switch (Aktivity / Profil) and reservation actions (Rezervovat / Uvolnit / Převzít) are the first items of ⋯ (RecordActionsMenu leadItems): choices with a tick on the active one, then a hairline, then the record actions. This is an approved exception to the MenuButton rule „choices all or none“ — the hairline separates the two groups (Linear does the same).
  • Reservation state lives as a row „Rezervace“ in Detail záznamu, not in the header.
  • Pattern source: Attio person (one row ‹ · avatar + name · ⋮) and Linear issue (‹ · ✎ · ⋯). Two-row header was rejected (takes too much space).
  • The header stays on top while the card scrolls (sticky, acrm !4369).
  • Extended to meeting, task, project (acrm !4394, verified on dev 1. 10. 2026): same one row ‹ · mark + name · ⋯ on the phone, sticky; meeting ⋯ starts with „Zeptat se AI nad touto schůzkou“; task has no ⋯ (no header actions). Team member in Settings: ✕ hidden on the phone, back is the Settings bar „← Nastavení“ only.
  • Menu items that cannot act yet are hidden, not disabled (no tooltip on touch): meeting exports appear only when minutes / a finished transcript exist.

Source: docs/section-top-bar/section-top-bar.usage.md · GitLab